﻿package utils 
{
	import flash.display.*;

	/**
	 * based on source code found at:
	 * http://www.macromedia.com/devnet/mx/flash/articles/adv_draw_methods.html
	 * 
	 * @author Ric Ewing - version 1.4 - 4.7.2002
	 * @author Kevin Williams - version 2.0 - 4.7.2005
	 * @author Aden Forshaw - Version AS3 - 19.4.2010
	 * @author Sidney de Koning - Version AS3 - 20.4.2010 - errors/correct datatypes/optimized math operations
	 *         Arie de Bonth - Version AS3 - 09.04.2011 - update/rewrite of drawLine() function
	 * 
	 * Usage:
	 * var s : Shape = new Shape( ); // Or Sprite of MovieClip or any other Class that makes use of the Graphics class
	 * 
	 * // Draw an ARC
	 * s.graphics.lineStyle( 4, 0xE16606 );
	 * DrawingShapes.drawArc( s.graphics, 50, 50, 10, 150, 60 );
	 * 
	 * // Draw an BURST	
	 * s.graphics.lineStyle( 3, 0x000000 );
	 * DrawingShapes.drawBurst( s.graphics, 80, 60, 3, 15, 6, 27 );
	 * 
	 * // Draw an DASHED-LINE like so - - - - 		
	 * s.graphics.lineStyle( 1, 0x3C3C39 );
	 * DrawingShapes.drawDash( s.graphics, 120, 60, 150, 80, 2, 2 );
	 * 
	 * // Draw an GEAR		
	 * s.graphics.lineStyle( 3, 0xE16606 );
	 * DrawingShapes.drawGear( s.graphics, 200, 60, 13, 31, 26, 0, 7, 13 );
	 * 
	 * // Draw a POLYGON		
	 * s.graphics.lineStyle( 3, 0x0074B9 );
	 * DrawingShapes.drawPolygon( s.graphics, 270, 60, 7, 30, 45 );
	 * 
	 * // Draw a STAR		
	 * s.graphics.lineStyle( 2, 0x000000 );
	 * DrawingShapes.drawStar( s.graphics, 340, 60, 18, 24, 19, 27 );
	 * 
	 * // Draw an WEDGE - good for pie charts or pacmans		
	 * s.graphics.lineStyle( 2, 0xFFCC00 );
	 * DrawingShapes.drawWedge( s.graphics, 400, 60, 30, 309, 209 );
	 * 
	 * // Draw a LINE	
	 * s.graphics.lineStyle( 2, 0x0074B9 );
	 * DrawingShapes.drawLine( s.graphics, 440, 85, 30, DrawingShapes.VERTICAL_LINE );
	 * 		
	 * addChild( s );
	 */
	public class DrawingShapes {

		public static const HORIZONTAL_LINE : String = "DrawingShapes.horizontal";
		public static const VERTICAL_LINE : String = "DrawingShapes.vertical";

		
		public function DrawingShapes() {
			throw new ArgumentError( "The DrawingShapes Class cannot be instanicated." );
		}

		/**
		 * drawDash
		 * Draws a dashed line from the point x1,y1 to the point x2,y2
		 * 
		 * @param target Graphics the Graphics Class on which the dashed line will be drawn.
		 * @param x1 Number starting position on x axis - <strong></strong>required</strong>
		 * @param y1 Number starting position on y axis - <strong></strong>required</strong>
		 * @param x2 Number finishing position on x axis - <strong></strong>required</strong>
		 * @param y2 Number finishing position on y axis - <strong></strong>required</strong>
		 * @param dashLength [optional] Number the number of pixels long each dash 
		 * will be.  Default = 5
		 * @param spaceLength [optional] Number the number of pixels between each 
		 * dash.  Default = 5
		 */
		public static function drawDash(target : Graphics, x1 : Number,y1 : Number,x2 : Number, y2 : Number, dashLength : Number = 5, spaceLength : Number = 5 ) : void {
			
			var x : Number = x2 - x1;
			var y : Number = y2 - y1;
			var hyp : Number = Math.sqrt( (x) * (x) + (y) * (y) );
			var units : Number = hyp / (dashLength + spaceLength);
			var dashSpaceRatio : Number = dashLength / (dashLength + spaceLength);
			var dashX : Number = (x / units) * dashSpaceRatio;
			var spaceX : Number = (x / units) - dashX;
			var dashY : Number = (y / units) * dashSpaceRatio;
			var spaceY : Number = (y / units) - dashY;
			
			target.moveTo( x1, y1 );
			while (hyp > 0) {
				x1 += dashX;
				y1 += dashY;
				hyp -= dashLength;
				if (hyp < 0) {
					x1 = x2;
					y1 = y2;
				}
				target.lineTo( x1, y1 );
				x1 += spaceX;
				y1 += spaceY;
				target.moveTo( x1, y1 );
				hyp -= spaceLength;
			}
			target.moveTo( x2, y2 );
		}

		/**
		 * Draws an arc from the starting position of x,y.
		 * 
		 * @param target the Graphics Class that the Arc is drawn on.
		 * @param x x coordinate of the starting pen position
		 * @param y y coordinate of the starting pen position 
		 * @param radius radius of Arc.
		 * @param arc = sweep of the arc. Negative values draw clockwise.
		 * @param startAngle = [optional] starting offset angle in degrees.
		 * @param yRadius = [optional] y radius of arc. if different than 
		 * radius, then the arc will draw as the arc of an oval.  
		 * default = radius.
		 *
		 * Based on mc.drawArc by Ric Ewing.
		 * the version by Ric assumes that the pen is at x:y before this
		 * method is called.  I explictily move the pen to x:y to be 
		 * consistent with the behaviour of the other methods.
		 */
		
		public static function drawArc(target : Graphics,  x : Number, y : Number, radius : Number, arc : Number, startAngle : Number = 0, yRadius : Number = 0) : void {
			
			if (arguments.length < 5) {
				throw new ArgumentError( "DrawingShapes.drawArc() - too few parameters, need atleast 5." );
				return;
			}
			
			// if startAngle is undefined, startAngle = 0
			if( startAngle == 0 ) {
				startAngle = 0;
			}
			// if yRadius is undefined, yRadius = radius
			if (yRadius == 0) {
				yRadius = radius;
			}
			
			// Init vars
			var segAngle : Number, theta : Number, angle : Number, angleMid : Number, segs : Number, ax : Number, ay : Number, bx : Number, by : Number, cx : Number, cy : Number;
			// no sense in drawing more than is needed :)
			if (DrawingShapes.abs( arc ) > 360) {
				arc = 360;
			}
			// Flash uses 8 segments per circle, to match that, we draw in a maximum
			// of 45 degree segments. First we calculate how many segments are needed
			// for our arc.
			segs = DrawingShapes.ceil( DrawingShapes.abs( arc ) / 45 );
			// Now calculate the sweep of each segment
			segAngle = arc / segs;
			// The math requires radians rather than degrees. To convert from degrees
			// use the formula (degrees/180)*Math.PI to get radians. 
			theta = -(segAngle / 180) * Math.PI;
			// convert angle startAngle to radians
			angle = -(startAngle / 180) * Math.PI;
			// find our starting points (ax,ay) relative to the secified x,y
			ax = x - Math.cos( angle ) * radius;
			ay = y - Math.sin( angle ) * yRadius;
			// if our arc is larger than 45 degrees, draw as 45 degree segments
			// so that we match Flash's native circle routines.
			if (segs > 0) {
				target.moveTo( x, y );
				// Loop for drawing arc segments
				for (var i : int = 0; i < segs; ++i) {
					// increment our angle
					angle += theta;
					// find the angle halfway between the last angle and the new
					angleMid = angle - (theta / 2);
					// calculate our end point
					bx = ax + Math.cos( angle ) * radius;
					by = ay + Math.sin( angle ) * yRadius;
					// calculate our control point
					cx = ax + Math.cos( angleMid ) * (radius / Math.cos( theta / 2 ));
					cy = ay + Math.sin( angleMid ) * (yRadius / Math.cos( theta / 2 ));
					// draw the arc segment
					target.curveTo( cx, cy, bx, by );
				}
			}
		}

		/**
		 * draws pie shaped wedges.  Could be employeed to draw pie charts.
		 * 
		 * @param target the Graphics on which the wedge is to be drawn.
		 * @param x x coordinate of the center point of the wedge
		 * @param y y coordinate of the center point of the wedge
		 * @param radius the radius of the wedge 
		 * @param arc the sweep of the wedge. negative values draw clockwise
		 * @param startAngle the starting angle in degrees
		 * @param yRadius [optional] the y axis radius of the wedge. 
		 * If not defined, then yRadius = radius.
		 * 
		 * based on mc.drawWedge() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
		 */
		public static function drawWedge(target : Graphics, x : Number, y : Number, radius : Number, arc : Number, startAngle : Number = 0,  yRadius : Number = 0) : void {
			
			// if yRadius is undefined, yRadius = radius
			if (yRadius == 0) {
				yRadius = radius;
			}
			
			// move to x,y position
			target.moveTo( x, y );
			// if yRadius is undefined, yRadius = radius
			if (yRadius == 0) {
				yRadius = radius;
			}
			// Init vars
			var segAngle : Number, theta : Number, angle : Number, angleMid : Number, segs : Number, ax : Number, ay : Number, bx : Number, by : Number, cx : Number, cy : Number;
			// limit sweep to reasonable numbers
			if (DrawingShapes.abs( arc ) > 360) {
				arc = 360;
			}
			// Flash uses 8 segments per circle, to match that, we draw in a maximum
			// of 45 degree segments. First we calculate how many segments are needed
			// for our arc.
			segs = DrawingShapes.ceil( DrawingShapes.abs( arc ) / 45 );
			// Now calculate the sweep of each segment.
			segAngle = arc / segs;
			// The math requires radians rather than degrees. To convert from degrees
			// use the formula (degrees/180)*Math.PI to get radians.
			theta = -(segAngle / 180) * Math.PI;
			// convert angle startAngle to radians
			angle = -(startAngle / 180) * Math.PI;
			// draw the curve in segments no larger than 45 degrees.
			if (segs > 0) {
				// draw a line from the center to the start of the curve
				ax = x + Math.cos( startAngle / 180 * Math.PI ) * radius;
				ay = y + Math.sin( -startAngle / 180 * Math.PI ) * yRadius;
				target.lineTo( ax, ay );
				// Loop for drawing curve segments
				for (var i : int = 0; i < segs; ++i) {
					angle += theta;
					angleMid = angle - (theta / 2);
					bx = x + Math.cos( angle ) * radius;
					by = y + Math.sin( angle ) * yRadius;
					cx = x + Math.cos( angleMid ) * (radius / Math.cos( theta / 2 ));
					cy = y + Math.sin( angleMid ) * (yRadius / Math.cos( theta / 2 ));
					target.curveTo( cx, cy, bx, by );
				}
				// close the wedge by drawing a line to the center
				target.lineTo( x, y );
			}
		}

		/**
		 * start draws a star shaped polygon.
		 * 
		 * <blockquote>Note that the stars by default 'point' to
		 * the right. This is because the method starts drawing
		 * at 0 degrees by default, putting the first point to
		 * the right of center. Negative values for points
		 * draws the star in reverse direction, allowing for
		 * knock-outs when used as part of a mask.</blockquote>
		 *  
		 * @param target the Graphics that the star is drawn on
		 * @param x x coordinate of the center of the star
		 * @param y y coordinate of the center of the star
		 * @param points the number of points on the star
		 * @param innerRadius the radius of the inside angles of the star
		 * @param outerRadius the radius of the outside angles of the star
		 * @param angle [optional] the offet angle that the start is rotated
		 * 
		 * based on mc.drawStar() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
		 */
		public static function drawStar(target : Graphics, x : Number, y : Number, points : uint, innerRadius : Number, outerRadius : Number, angle : Number = 0) : void {
			
			// check that points is sufficient to build polygon
			if(points <= 2) {
				throw ArgumentError( "DrawingShapes.drawStar() - parameter 'points' needs to be atleast 3" ); 
				return;
			}
			if (points > 2) {
				// init vars
				var step : Number, halfStep : Number, start : Number, n : Number, dx : Number, dy : Number;
				// calculate distance between points
				step = (Math.PI * 2) / points;
				halfStep = step / 2;
				// calculate starting angle in radians
				start = (angle / 180) * Math.PI;
				target.moveTo( x + (Math.cos( start ) * outerRadius), y - (Math.sin( start ) * outerRadius) );
				// draw lines
				for (n = 1; n <= points; ++n) {
					dx = x + Math.cos( start + (step * n) - halfStep ) * innerRadius;
					dy = y - Math.sin( start + (step * n) - halfStep ) * innerRadius;
					target.lineTo( dx, dy );
					dx = x + Math.cos( start + (step * n) ) * outerRadius;
					dy = y - Math.sin( start + (step * n) ) * outerRadius;
					target.lineTo( dx, dy );
				}
			}
		}
		
		/**
		 * a method for creating polygon shapes.  Negative values will draw 
		 * the polygon in reverse direction.  Negative drawing may be useful 
		 * for creating knock-outs in masks.
		 * 
		 * @param target the Graphics that the polygon is to be drawn on
		 * @param x x coordinate of the center of the polygon
		 * @param y y coordinate of the center of the polygon
		 * @param sides the number of sides (must be > 2)
		 * @param radius the radius from the center point to the points
		 * on the polygon
		 * @param angle [optional] the starting offset angle (degrees) from
		 * 0. Default = 0
		 * 
		 * based on mc.drawPoly() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
		 */
		public static function drawPolygon(target : Graphics, x : Number, y : Number, sides : uint, radius : Number, angle : Number = 0) : void {
			
			// check that sides is sufficient to build
			if(sides <= 2) {
				throw ArgumentError( "DrawingShapes.drawPolygon() - parameter 'sides' needs to be atleast 3" ); 
				return;
			}
			if (sides > 2) {
				// init vars
				var step : Number, start : Number, n : Number, dx : Number, dy : Number;
				// calculate span of sides
				step = (Math.PI * 2) / sides;
				// calculate starting angle in radians
				start = (angle / 180) * Math.PI;
				target.moveTo( x + (Math.cos( start ) * radius), y - (Math.sin( start ) * radius) );
				// draw the polygon
				for (n = 1; n <= sides; ++n) {
					dx = x + Math.cos( start + (step * n) ) * radius;
					dy = y - Math.sin( start + (step * n) ) * radius;
					target.lineTo( dx, dy );
				}
			}
		}

		/**
		 * Burst is a method for drawing star bursts.  If you've ever worked
		 * with an advertising department, you know what they are ;-)
		 * Clients tend to want them, Developers tend to hate them...
		 *
		 * @param target Graphics where the Burst is to be drawn.
		 * @param x x coordinate of the center of the burst
		 * @param y y coordinate of the center of the burst
		 * @param sides number of sides or points
		 * @param innerRadius radius of the indent of the curves
		 * @param outerRadius radius of the outermost points
		 * @param angle [optional] starting angle in degrees. (defaults to 0)
		 * 
		 * based on mc.drawBurst() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
		 */
		public static function drawBurst(target : Graphics, x : Number, y : Number, sides : uint, innerRadius : Number, outerRadius : Number, angle : Number = 0 ) : void {
			
			// check that sides is sufficient to build
			if(sides <= 2) {
				throw ArgumentError( "DrawingShapes.drawBurst() - parameter 'sides' needs to be atleast 3" ); 
				return;
			}
			if (sides > 2) {
				// init vars
				var step : Number, halfStep : Number, qtrStep : Number, start : Number, n : Number, dx : Number, dy : Number, cx : Number, cy : Number;
				// calculate length of sides
				step = (Math.PI * 2) / sides;
				halfStep = step / 2;
				qtrStep = step / 4;
				// calculate starting angle in radians
				start = (angle / 180) * Math.PI;
				target.moveTo( x + (Math.cos( start ) * outerRadius), y - (Math.sin( start ) * outerRadius) );
				// draw curves
				for (n = 1; n <= sides; ++n) {
					cx = x + Math.cos( start + (step * n) - (qtrStep * 3) ) * (innerRadius / Math.cos( qtrStep ));
					cy = y - Math.sin( start + (step * n) - (qtrStep * 3) ) * (innerRadius / Math.cos( qtrStep ));
					dx = x + Math.cos( start + (step * n) - halfStep ) * innerRadius;
					dy = y - Math.sin( start + (step * n) - halfStep ) * innerRadius;
					target.curveTo( cx, cy, dx, dy );
					cx = x + Math.cos( start + (step * n) - qtrStep ) * (innerRadius / Math.cos( qtrStep ));
					cy = y - Math.sin( start + (step * n) - qtrStep ) * (innerRadius / Math.cos( qtrStep ));
					dx = x + Math.cos( start + (step * n) ) * outerRadius;
					dy = y - Math.sin( start + (step * n) ) * outerRadius;
					target.curveTo( cx, cy, dx, dy );
				}
			}
		}

		/**
		 * draws a gear shape on the Graphics target.  The gear position 
		 * is indicated by the x and y arguments.
		 * 
		 * @param target Graphics on which the gear is to be drawn.
		 * @param x x coordinate of the center of the gear
		 * @param y y coordinate of the center of the gear
		 * @param sides number of teeth on gear. (must be > 2)
		 * @param innerRadius radius of the indent of the teeth.
		 * @param outerRadius outer radius of the teeth.
		 * @param angle = [optional] starting angle in degrees. Defaults to 0.
		 * @param holeSides [optional] draw a polygonal hole with this many sides (must be > 2)
		 * @param holeRadius [optional] size of hole. Default = innerRadius/3.
		 * 
		 * based on mc.drawGear() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
		 */
		public static function drawGear(target : Graphics, x : Number, y : Number, sides : uint, innerRadius : Number = 80, outerRadius : Number = 4, angle : Number = 0, holeSides : Number = 2, holeRadius : Number = 0 ) : void {
			
			// check that sides is sufficient to build polygon
			if(sides <= 2) {
				throw ArgumentError( "DrawingShapes.drawGear() - parameter 'sides' needs to be atleast 3" ); 
				return;
			}
			if (sides > 2) {
				// init vars
				var step : Number, qtrStep : Number, start : Number, n : Number, dx : Number, dy : Number;
				// calculate length of sides
				step = (Math.PI * 2) / sides;
				qtrStep = step / 4;
				// calculate starting angle in radians
				start = (angle / 180) * Math.PI;
				target.moveTo( x + (Math.cos( start ) * outerRadius), y - (Math.sin( start ) * outerRadius) );
				// draw lines
				for (n = 1; n <= sides; ++n) {
					dx = x + Math.cos( start + (step * n) - (qtrStep * 3) ) * innerRadius;
					dy = y - Math.sin( start + (step * n) - (qtrStep * 3) ) * innerRadius;
					target.lineTo( dx, dy );
					dx = x + Math.cos( start + (step * n) - (qtrStep * 2) ) * innerRadius;
					dy = y - Math.sin( start + (step * n) - (qtrStep * 2) ) * innerRadius;
					target.lineTo( dx, dy );
					dx = x + Math.cos( start + (step * n) - qtrStep ) * outerRadius;
					dy = y - Math.sin( start + (step * n) - qtrStep ) * outerRadius;
					target.lineTo( dx, dy );
					dx = x + Math.cos( start + (step * n) ) * outerRadius;
					dy = y - Math.sin( start + (step * n) ) * outerRadius;
					target.lineTo( dx, dy );
				}
				// This is complete overkill... but I had it done already. :)
				if (holeSides > 2) {
					step = (Math.PI * 2) / holeSides;
					target.moveTo( x + (Math.cos( start ) * holeRadius), y - (Math.sin( start ) * holeRadius) );
					for (n = 1; n <= holeSides; ++n) {
						dx = x + Math.cos( start + (step * n) ) * holeRadius;
						dy = y - Math.sin( start + (step * n) ) * holeRadius;
						target.lineTo( dx, dy );
					}
				}
			}
		}

		/**
		 * draws a line between two points. Make it horizontal or vertical 
		 * 
		 * @param target Graphics on which the gear is to be drawn.
		 * @param x x coordinate of the center of the gear
		 * @param y y coordinate of the center of the gear
		 * @param sides number of teeth on gear. (must be > 2)
		 * 
		 * 
		 */
		public static function drawLine( target : Graphics, x : Number, y : Number, length : Number, direction : String = DrawingShapes.HORIZONTAL_LINE ) : void {
			target.moveTo( x, y );
			switch (direction) {
				case DrawingShapes.HORIZONTAL_LINE :
					target.lineTo( x + length, y );
					break;
				case DrawingShapes.VERTICAL_LINE :
					target.lineTo( x, y + length );
					break;
			}
		}

		/*
		 * new abs function, about 25x faster than Math.abs
		 */
		private static function abs( value : Number ) : Number {
			return value < 0 ? -value : value;
		}

		/*
		 * new ceil function about 75% faster than Math.ceil. 
		 */
		private static function ceil( value : Number) : Number {
			return (value % 1) ? int( value ) + 1 : value;
		}
	}
}